/*
 * Copyright 2011 Dominik Pretzsch <dominik.pretzsch at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.blacksheep.jmoteextract.archive;

import de.blacksheep.jmoteextract.archive.callback.ArchiveExtractCallback;
import de.blacksheep.jmoteextract.archive.strategy.ArchiveOpenStrategy;
import de.blacksheep.jmoteextract.remote.IRemoteObserver;
import de.blacksheep.jmoteextract.remote.RemoteProgressObservable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;

/**
 * <code>Archive</code> is providing the main functionality to <br>
 * work with an inArchive file and represents the <br>
 * actual inArchive itself. <br>
 * <br>
 * It provides a basic <code>open(), close()</code> and <code>extract()</code> <br>
 * interface. <br>
 * <br>
 * A <code>PipedWriter</code> can be set to handle progress <br>
 * information between threads. <br>
 * The progress communication protocol is described <br>
 * in {@see ArchiveExtractCallback} class. <br>
 *
 * @author Dominik Pretzsch <dominik.pretzsch at gmail.com>
 * @see #progressWriter
 * @see ArchiveExtractCallback
 */
public class Archive {
    
    private RemoteProgressObservable observable;
    private IRemoteObserver          observer;
    private String  absolutePath;
    private String  absoluteExtractionPath;
    private String  fileName;    
    private String  fileExtension;
    private File    file; 
    private int     hash;
    private int     row               = 0;
    private boolean isStarted         = false;
    private boolean isExtracted       = false;
    private boolean isNonLocalArchive = false;
               
    private ISevenZipInArchive inArchive       = null;    
    private ArchiveOpenContext strategyContext = null;
    
    private static enum  MULTI_TYPE { Multipart7z, MultipartRar, SingleFile };  
    private static final Character  EXT_SEPARATOR           = '.';      
    private static final String     PATTERN_MULTI_RAR       = ".*(\\.part\\d+\\.rar)";    
    private static final String     PATTERN_MULTI_SEVEN_ZIP = ".*(\\.7z\\.\\d+)";    
    
    private static final Logger LOGGER =  Logger.getLogger(Archive.class.getName());
    
    public Archive() {
        this(0, null, null);
    }
    
    public Archive(String absolutePath) {
        this(0, absolutePath, null);
    }
    
    public Archive(int hash, String absolutePath) {
        this(hash, absolutePath, null);
    }
    
    public Archive(String absolutePath, String absoluteExtractionPath) {
        this(0, absolutePath, absoluteExtractionPath);
    }
    
    public Archive(int hash, 
                   String absolutePath, 
                   String absoluteExtractionPath) {
        
        this.hash = hash;
        this.absolutePath = absolutePath;
        this.absoluteExtractionPath = absoluteExtractionPath;
    }
    
    public int getHash() {
        return this.hash;
    }
    
    public void setHash(int hash) {
        this.hash = hash;
    }
    
    public String getAbsolutePath() {
        return this.absolutePath;
    }
    
    public void setAbsolutePath(String absolutePath) {
        this.absolutePath = absolutePath;
    }
    
    public String getAbsoluteExtractionPath() {
        return this.absoluteExtractionPath;
    }
    
    public void setAbsoluteExtractionPath(String absoluteExtractionPath) {
        LOGGER.log(Level.FINE, "Setting extractionPath to {0}", absoluteExtractionPath);
        this.absoluteExtractionPath = absoluteExtractionPath;
    }
    
    public String getFileName() {
        if (fileName == null) {
            fileName = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1);
        }
        return fileName;
    }
    
    public String getFileExtension() {
        if (fileExtension == null) {
            fileExtension = getFileName().substring(fileName.lastIndexOf(EXT_SEPARATOR));
        }
        return fileExtension;
    }
    
    public boolean isNonLocalArchive() {
        return isNonLocalArchive;
    }
    
    public void setIsNonLocalArchive(boolean isNonLocalArchive) {
        this.isNonLocalArchive = isNonLocalArchive;
    }

    public boolean isStarted() {
        return isStarted;
    }
    
    public void setStarted(boolean isStarted) {
        this.isStarted = isStarted;
    }

    public boolean isExtracted() {
        return isExtracted;
    }
    
    public void setExtracted(boolean isExtracted) {
        this.isExtracted = isExtracted;
    }
    
    public int getRow() {
        return row;
    }
    
    public void setRow(int row) {
        this.row = row;
    }
    
    public void open() {
        LOGGER.log(Level.FINE, "Try to open {0}", absolutePath);
        try {
            openFile();
            openArchive();
        } catch (SevenZipException ex) {
            // TODO: SevenZipException
            LOGGER.log(Level.SEVERE, "SevenZipException while open()", ex);
        } 
        catch (FileNotFoundException ex) {
            // TODO: FileNotFoundException
            LOGGER.log(Level.SEVERE, "FileNotFoundException while open()", ex);
        }
    }
    
    public ISevenZipInArchive getInArchive() {
        return this.inArchive;
    }
    
    public void close() {
        LOGGER.log(Level.FINE, "Try to close {0}", absolutePath);
        try {
            strategyContext.close();
        } catch (IOException ex) {
            // TODO: IOException
            LOGGER.log(Level.SEVERE, "IOException while close()", ex);
        } catch (SevenZipException ex) {
            // TODO: SevenZipException
            LOGGER.log(Level.SEVERE, "SevenZipException while close()", ex);
        }
    }
    
    public void extract() {
        LOGGER.log(Level.FINE, "Try to extract {0}", absolutePath);
        try {
            ArchiveExtractor extractor = new ArchiveExtractor(this);            
            extractor.extract();
        } catch (SevenZipNativeInitializationException ex) {
            // TODO: SevenZipNativeInitializationException
            LOGGER.log(Level.SEVERE, "Error loading 7-Zip-JBinding", ex);
        } catch (SevenZipException ex) {
            // TODO: SevenZipException
            LOGGER.log(Level.SEVERE, "SevenZipException while extract()", ex);
        }
    }
    
    public boolean hasObserver() {
        return (observer == null) ? false : true;
    }
    
    public void assignObserver(IRemoteObserver remoteObserver) {
        this.observer = remoteObserver;
    }
    
    public IRemoteObserver getObserver() {
        return observer;
    }
    
    public boolean isObservable() {
        return (observable == null) ? false : true;
    }
    
    public void makeObservable() {
        try {
            observable = new RemoteProgressObservable();
        } catch (RemoteException ex) {
            // TODO: RemoteException
            LOGGER.log(Level.SEVERE, "RemoteException while makeRemoteObservable()", ex);
        }
    }
    
    public void addObserverToObservable() {
        try {
            observable.addObserver(observer);
        } catch (RemoteException ex) {
            // TODO: RemoteException
            LOGGER.log(Level.SEVERE, "RemoteException while connectObserver()", ex);
        }
    }
    
    public RemoteProgressObservable getObservable() {
        return observable;
    }
    
    private void openFile() 
                 throws FileNotFoundException {
                     
        LOGGER.log(Level.FINE, "Try to openFile {0}", absolutePath);
        
        file = new File(absolutePath);
        if (!file.exists()) {
            throw new FileNotFoundException();
        }
    }
    
    private void openArchive() 
                 throws SevenZipException, FileNotFoundException {
                     
        LOGGER.log(Level.FINE, "Try to openArchive {0}", absolutePath);
        
        MULTI_TYPE type = determineMultiType();
        loadOpenStrategyContext(type);        
        
        if (strategyContext != null) {
            LOGGER.log(Level.FINE, "Try to open archive with chosen strategy");
            inArchive = strategyContext.open(file);
        }   
    }
    
    private MULTI_TYPE determineMultiType() {
        MULTI_TYPE type = MULTI_TYPE.SingleFile;
        
        if (getFileName().matches(PATTERN_MULTI_RAR)) {
            LOGGER.log(Level.FINE, "Archive is MULTI_RAR");
            type = MULTI_TYPE.MultipartRar;
        } else if (getFileName().matches(PATTERN_MULTI_SEVEN_ZIP)){
            LOGGER.log(Level.FINE, "Archive is MULTI_SEVEN_ZIP");
            type = MULTI_TYPE.Multipart7z;
        } else {
            LOGGER.log(Level.FINE, "Archive is NO_MULTI");
        }
        
        return type;
    }
    
    private void loadOpenStrategyContext(MULTI_TYPE type) {        
        String strategyPath = "de.blacksheep.jmoteextract.archive.strategy.";
        String strategyBase = "ArchiveOpenStrategy";
        String strategyFile = strategyPath.concat(strategyBase).concat(type.name());
        
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ArchiveOpenStrategy strategy = null;
        try {
            LOGGER.log(Level.FINE, "Try to load {0}...", strategyFile);
            strategy = (ArchiveOpenStrategy) cl.loadClass(strategyFile).newInstance();
            strategyContext = new ArchiveOpenContext(strategy);
        } catch (InstantiationException ex) {
            Logger.getLogger(Archive.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(Archive.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
            LOGGER.log(Level.FINE, "Can't load {0}. Using SingleFile...", strategyFile);
            loadOpenStrategyContext(MULTI_TYPE.SingleFile);
        }
    }
}
